package com.xy.tool.gid.service.impl;

import lombok.extern.slf4j.Slf4j;
import com.xy.lang.util.NetUtils;
import com.xy.tool.gid.autoconfigure.SnowflakeProperties;
import com.xy.tool.gid.domain.GidNode;
import com.xy.tool.gid.service.GidNodeService;
import com.xy.tool.gid.service.GidService;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Created by zsp on 2018/7/11.
 */
@Slf4j
@Service
public class GidServiceImpl implements GidService, InitializingBean {

    private final ReentrantLock lock = new ReentrantLock();

    private volatile long machineNo;
    private volatile long currentMillis = 0;
    private volatile long incr;

    @Autowired
    private SnowflakeProperties properties;
    @Autowired
    private GidNodeService gidNodeService;

    @Value("${server.port}")
    private Integer serverPort;

    @Override
    public void afterPropertiesSet() throws Exception {
        if(serverPort == null) {
            throw new IllegalStateException("未配置: server.port");
        }
        machineNo = getSequence(NetUtils.getHostAddress(), getServerPort());
    }

    private int getSequence(String ip, int port) {
        Objects.requireNonNull(ip, "The ip cannot be null.");
        if(port < 1) {
            port = getServerPort();
        }
        Integer sequence = gidNodeService.getSequenceOrUpdateEmptyNode(ip, port);
        if(sequence != null) {
            log.info("get sequence [" + sequence + "] by " + ip + ":" + port);
            return sequence.intValue();
        }
        log.warn("The sequence for " + ip + ":" + port + " is not defined.");
        int hash = ip.hashCode() & GidNode.MAX_SERVER_SIZE_MASK;
        log.info("get sequence [" + hash + "] by " + ip + " hash code.");
        return hash;
    }

    private int getServerPort() {
        if(serverPort != null) {
            return serverPort;
        }
        return 8080;
    }

    @Override
    public long generate() {
        try{
            //上锁防止单个节点每毫秒的自增数被耗尽而超用
            lock.lock();
            long millis = System.currentTimeMillis();
            if (currentMillis < millis) {
                //更新当前毫秒，并且自增数归零
                currentMillis = millis;
                incr = 0;
            }
            if (incr >= 0x0FFF) {
                incr = 0;
                //如果当前自增数已达最大值，则归零；判断最新毫秒数，是否已流逝到下一毫秒，否则线程自旋等待时间的流逝
                long m = currentMillis;
                long latestMillis = System.currentTimeMillis();
                while (latestMillis <= m) {
                    Thread.yield();
                    latestMillis = System.currentTimeMillis();
                }
                millis = latestMillis;
                currentMillis = millis;
            }
            //41位时间戳 + 10位机器序号 + 12位自增序列
            return ((millis - properties.getOffset()) << 22) | (machineNo << 12) | incr++;
        } finally {
            lock.unlock();
        }
    }

    @Override
    public List<Long> generate(int size) {
        if(size < 1) {
            throw new IllegalArgumentException("The size cannot be small than 1.");
        }
        List<Long> list = new ArrayList<>();
        for(int i = 0, len = size; i < len; i++) {
            list.add(generate());
        }
        return list;
    }

}
